# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: inkyu

import datetime
import logging

import tabulate
import pandas

from coin.exchange.shared.kr_rest.currency import SharedCurrency
from coin.strategy.cheese.config import L2Config


def _bal_format(reserve, each_bal):
  if reserve > 0:
    return "%.2f(%4d%%)" % (each_bal, each_bal / reserve * 1e2)
  else:
    return "%.2f(%4d%%)" % (each_bal, 0.)


def print_prices_table(logger, currencys, balances, priceses, reserves, timestamp):
  headers = [
      '*',
      'currency',
      'ex0_bal',
      'base_total',
      'base_reserve',
      'ex0_price',
      'ex1_price',
      '1/buy_pa',
      '1/sell_pa',
      'buy_pa',
      'sell_pa',
      'buy_pp',
      'sell_pp',
      'timestamp',
      'premium_age_sec'
  ]
  table = []
  if priceses is None:
    return
  for currency, balance, prices, reserve in zip(currencys, balances, priceses, reserves):
    data = ['*'] + [currency]
    if balance is not None:
      balsum = balance.base_balance
      data += [_bal_format(reserve, balance.base_balance)]
      data += [_bal_format(reserve, balsum), "%.2f" % reserve]
    else:
      pass
    if prices is not None:
      data += ([prices.midp0, prices.midp1] + [1. / prices.buy_price_pa, 1. / prices.sell_price_pa]
               + [prices.buy_price_pa, prices.sell_price_pa]
               + [prices.buy_price_pp, prices.sell_price_pp]
               + pandas.DatetimeIndex([prices.timestamp]).strftime("%Y%m%d %H:%M:%S").tolist()
               + [(timestamp - prices.timestamp) * 1e-9])
    else:
      data += [None, None, None, None, None, None, None, None]
    table.append(data)
  table.sort(key=lambda row: row[headers.index('buy_pa')]
             if row[headers.index('buy_pa')] is not None else 0)
  logger.info('----- Premium:\n* %s\n%s' %
              (str(datetime.datetime.now()),
               tabulate.tabulate(table, headers, floatfmt=".4f", stralign="right")))


class BalancePrinter(object):
  def __init__(self,
               *,
               quote_currency,
               base_currencies,
               extra_currencies=None,
               base_currency_reserves,
               og):
    self._check_init_param_consistency(base_currencies, base_currency_reserves)
    self._quote_currency = quote_currency
    self._extra_currencies = extra_currencies
    self._base_currencies = base_currencies
    self._base_currency_reserves = base_currency_reserves
    self._og = og
    self._common_currencies = self._get_common_currencies()

    self._product_prices = {}
    self._logger = logging.getLogger(__name__)

  def _get_common_currencies(self):
    currency_set = {
        SharedCurrency.FromCurrency(currency) for currency in self._og.og_info.currencies
    }
    return currency_set

  def set_product_price(self, product, price):
    self._product_prices[product] = price

  @staticmethod
  def _check_init_param_consistency(base_currencies, base_currency_reserves):
    assert set(base_currencies) == set(base_currency_reserves), (base_currencies,
                                                                 base_currency_reserves)

  def on_book(self, product, exchange_id, book):
    currency = SharedCurrency.FromCurrency(product.base)
    # For common currency, use only exchange 0 for estimation. For special
    # currency, use what ever exchange that provides the price.
    if currency in self._common_currencies:
      if exchange_id == 0:
        assert product.quote == self._quote_currency, (product.quote, self._quote_currency)
        mid_price = 0.5 * (book.ask0().price + book.bid0().price)
        self.set_product_price(currency, mid_price)
    else:
      raise Exception('Unreachable')

  def print_balance(self):
    table = []
    quote_balance = self._og.get_balance(self._quote_currency)
    if quote_balance is None:
      return
    table = [{
        'currency': self._quote_currency,
        'total': quote_balance,
    }]

    if self._extra_currencies:
      for cur, value in self._extra_currencies.items():
        balance = self._og.get_balance(cur)
        if balance is None:
          return
        table.append({
            'currency': cur,
            'total': balance,
        })

    og_currencies = [currency.currency for currency in self._og.og_info.currencies]
    for cur in self._base_currencies:
      balance = None
      if cur.currency in og_currencies:
        balance = self._og.get_balance(cur.currency)

      table.append({
          'currency': cur,
          'total': balance,
      })

    quote = 0
    adjusted_quote = 0
    estimated_base = 0
    adjusted_estimated_base = 0
    imbalance_total = 0.
    extra_value = 0.

    for entry in table:
      currency = entry['currency']
      reserve = self._base_currency_reserves.get(currency, 0.)

      if self._extra_currencies and entry['currency'] in self._extra_currencies:
        # Only support aggressive
        price = self._extra_currencies[entry['currency']] * self._product_prices.get(
            self._quote_currency, None)
        if price is None:
          self._logger.warning('%s is not ready', self._quote_currency)
          return
        entry['reserve'] = reserve
        entry['imbalance'] = 0
        entry['imbalance_amount'] = 0
        entry['estimated_base'] = 0
        entry['adjusted_estimated_base'] = 0
        adjusted_quote += entry['total'] * price
        extra_value += entry['total'] * price
      else:
        price = self._product_prices.get(currency, None)
        if price is None:
          self._logger.warning('%s is not ready', currency)
          return
        if entry['currency'] != self._quote_currency:
          entry['reserve'] = reserve
          entry['imbalance'] = entry['total'] - reserve
          entry['imbalance_amount'] = entry['imbalance'] * price
          entry['estimated_base'] = entry['total'] * price
          entry['adjusted_estimated_base'] = entry['reserve'] * price
          adjusted_quote += entry['imbalance_amount']
          imbalance_total += entry['imbalance_amount']
          estimated_base += entry['estimated_base']
          adjusted_estimated_base += entry['adjusted_estimated_base']
        else:
          entry['reserve'] = reserve
          entry['imbalance'] = 0
          entry['imbalance_amount'] = 0
          entry['estimated_base'] = 0
          entry['adjusted_estimated_base'] = 0
          adjusted_quote += entry['total'] * price
          quote += entry['total'] * price

    quote = self._og.get_balance(self._quote_currency) + extra_value

    headers = (['Currency'] + [self._og.exchange] + [
        'Total',
        'Reserve',
        'Imbalance',
        'ImbalanceAmount',
        'EstimatedBase',
        'AdjustedEstimatedBase'
    ])
    fmt_table = []
    for entry in table:
      fmt_table.append([entry['currency']] + [
          entry['total'],
          entry['reserve'],
          entry['imbalance'],
          entry['imbalance_amount'],
          entry['estimated_base'],
          entry['adjusted_estimated_base']
      ])

    return
    # TODO(inkyu) Revive?
    res = ("%s\n%s: %s, Imbalance: %s, Adjusted%s: %s, EstimatedBase: %s\n"
           "Position%s: %s (%.2f%%) \n"
           "Balance%s: %s \n"
           "Reserve%s: %s \n") % (
               tabulate.tabulate(fmt_table, headers, floatfmt='.4f', stralign="right"),
               self._quote_currency,
               quote,
               imbalance_total,
               self._quote_currency,
               quote + imbalance_total,
               estimated_base,
               self._quote_currency,
               self._pos_amts.position_amt if self._pos_amts else 0.,
               (100. * self._pos_amts.position_amt
                / self._pos_amts.reserve_amt if self._pos_amts else 0.),
               self._quote_currency,
               self._pos_amts.balance_amt if self._pos_amts else 0.,
               self._quote_currency,
               self._pos_amts.reserve_amt if self._pos_amts else 0.,
           )
    self._logger.info('----- Estimated Balance:\n* %s\n%s' % (str(datetime.datetime.now()), res))
